{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建卷积神经网络\n",
    "- 卷积网络中的输入和层与传统神经网络有些区别，需重新设计，训练模块基本一致"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "import torch.nn.functional as F\n",
    "from torchvision import datasets,transforms \n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 首先读取数据\n",
    "- 分别构建训练集和测试集（验证集）\n",
    "- DataLoader来迭代取数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义超参数 \n",
    "input_size = 28  #图像的总尺寸28*28\n",
    "num_classes = 10  #标签的种类数\n",
    "num_epochs = 3  #训练的总循环周期\n",
    "batch_size = 64  #一个撮（批次）的大小，64张图片\n",
    "\n",
    "# 训练集\n",
    "train_dataset = datasets.MNIST(root='./data',  \n",
    "                            train=True,   \n",
    "                            transform=transforms.ToTensor(),  \n",
    "                            download=True) \n",
    "\n",
    "# 测试集\n",
    "test_dataset = datasets.MNIST(root='./data', \n",
    "                           train=False, \n",
    "                           transform=transforms.ToTensor())\n",
    "\n",
    "# 构建batch数据\n",
    "train_loader = torch.utils.data.DataLoader(dataset=train_dataset, \n",
    "                                           batch_size=batch_size, \n",
    "                                           shuffle=True)\n",
    "test_loader = torch.utils.data.DataLoader(dataset=test_dataset, \n",
    "                                           batch_size=batch_size, \n",
    "                                           shuffle=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 卷积网络模块构建\n",
    "- 一般卷积层，relu层，池化层可以写成一个套餐\n",
    "- 注意卷积最后结果还是一个特征图，需要把图转换成向量才能做分类或者回归任务"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CNN(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(CNN, self).__init__()\n",
    "        self.conv1 = nn.Sequential(         # 输入大小 (1, 28, 28)\n",
    "            nn.Conv2d(\n",
    "                in_channels=1,              # 灰度图\n",
    "                out_channels=16,            # 要得到几多少个特征图\n",
    "                kernel_size=5,              # 卷积核大小\n",
    "                stride=1,                   # 步长\n",
    "                padding=2,                  # 如果希望卷积后大小跟原来一样，需要设置padding=(kernel_size-1)/2 if stride=1\n",
    "            ),                              # 输出的特征图为 (16, 28, 28)\n",
    "            nn.ReLU(),                      # relu层\n",
    "            nn.MaxPool2d(kernel_size=2),    # 进行池化操作（2x2 区域）, 输出结果为： (16, 14, 14)\n",
    "        )\n",
    "        self.conv2 = nn.Sequential(         # 下一个套餐的输入 (16, 14, 14)\n",
    "            nn.Conv2d(16, 32, 5, 1, 2),     # 输出 (32, 14, 14)\n",
    "            nn.ReLU(),                      # relu层\n",
    "            nn.Conv2d(32, 32, 5, 1, 2),\n",
    "            nn.ReLU(),\n",
    "            nn.MaxPool2d(2),                # 输出 (32, 7, 7)\n",
    "        )\n",
    "        \n",
    "        self.conv3 = nn.Sequential(         # 下一个套餐的输入 (16, 14, 14)\n",
    "            nn.Conv2d(32, 64, 5, 1, 2),     # 输出 (32, 14, 14)\n",
    "            nn.ReLU(),             # 输出 (32, 7, 7)\n",
    "        )\n",
    "        \n",
    "        self.out = nn.Linear(64 * 7 * 7, 10)   # 全连接层得到的结果\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.conv1(x)\n",
    "        x = self.conv2(x)\n",
    "        x = self.conv3(x)\n",
    "        x = x.view(x.size(0), -1)           # flatten操作，结果为：(batch_size, 32 * 7 * 7)\n",
    "        output = self.out(x)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 准确率作为评估标准"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def accuracy(predictions, labels):\n",
    "    pred = torch.max(predictions.data, 1)[1] \n",
    "    rights = pred.eq(labels.data.view_as(pred)).sum() \n",
    "    return rights, len(labels) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 训练网络模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "当前epoch: 0 [0/60000 (0%)]\t损失: 2.302855\t训练集准确率: 9.38%\t测试集正确率: 10.18%\n",
      "当前epoch: 0 [6400/60000 (11%)]\t损失: 2.304197\t训练集准确率: 10.41%\t测试集正确率: 10.54%\n",
      "当前epoch: 0 [12800/60000 (21%)]\t损失: 2.299783\t训练集准确率: 10.49%\t测试集正确率: 10.93%\n",
      "当前epoch: 0 [19200/60000 (32%)]\t损失: 2.299291\t训练集准确率: 10.84%\t测试集正确率: 10.97%\n",
      "当前epoch: 0 [25600/60000 (43%)]\t损失: 2.300455\t训练集准确率: 11.00%\t测试集正确率: 11.10%\n",
      "当前epoch: 0 [32000/60000 (53%)]\t损失: 2.298190\t训练集准确率: 10.84%\t测试集正确率: 11.71%\n",
      "当前epoch: 0 [38400/60000 (64%)]\t损失: 2.298532\t训练集准确率: 11.07%\t测试集正确率: 11.95%\n",
      "当前epoch: 0 [44800/60000 (75%)]\t损失: 2.299254\t训练集准确率: 11.20%\t测试集正确率: 12.12%\n",
      "当前epoch: 0 [51200/60000 (85%)]\t损失: 2.293255\t训练集准确率: 11.30%\t测试集正确率: 12.42%\n",
      "当前epoch: 0 [57600/60000 (96%)]\t损失: 2.294169\t训练集准确率: 11.54%\t测试集正确率: 14.21%\n",
      "当前epoch: 1 [0/60000 (0%)]\t损失: 2.293650\t训练集准确率: 9.38%\t测试集正确率: 16.28%\n",
      "当前epoch: 1 [6400/60000 (11%)]\t损失: 2.287533\t训练集准确率: 17.53%\t测试集正确率: 18.52%\n",
      "当前epoch: 1 [12800/60000 (21%)]\t损失: 2.288062\t训练集准确率: 19.02%\t测试集正确率: 21.33%\n",
      "当前epoch: 1 [19200/60000 (32%)]\t损失: 2.286392\t训练集准确率: 20.12%\t测试集正确率: 21.93%\n",
      "当前epoch: 1 [25600/60000 (43%)]\t损失: 2.285017\t训练集准确率: 20.79%\t测试集正确率: 23.45%\n",
      "当前epoch: 1 [32000/60000 (53%)]\t损失: 2.287312\t训练集准确率: 21.63%\t测试集正确率: 25.13%\n",
      "当前epoch: 1 [38400/60000 (64%)]\t损失: 2.291477\t训练集准确率: 22.50%\t测试集正确率: 26.34%\n",
      "当前epoch: 1 [44800/60000 (75%)]\t损失: 2.281468\t训练集准确率: 23.28%\t测试集正确率: 27.43%\n",
      "当前epoch: 1 [51200/60000 (85%)]\t损失: 2.272476\t训练集准确率: 24.07%\t测试集正确率: 31.53%\n",
      "当前epoch: 1 [57600/60000 (96%)]\t损失: 2.269137\t训练集准确率: 24.78%\t测试集正确率: 28.82%\n",
      "当前epoch: 2 [0/60000 (0%)]\t损失: 2.275979\t训练集准确率: 21.88%\t测试集正确率: 32.24%\n",
      "当前epoch: 2 [6400/60000 (11%)]\t损失: 2.286332\t训练集准确率: 34.16%\t测试集正确率: 37.78%\n",
      "当前epoch: 2 [12800/60000 (21%)]\t损失: 2.258938\t训练集准确率: 36.67%\t测试集正确率: 38.21%\n",
      "当前epoch: 2 [19200/60000 (32%)]\t损失: 2.260726\t训练集准确率: 37.33%\t测试集正确率: 41.07%\n",
      "当前epoch: 2 [25600/60000 (43%)]\t损失: 2.262285\t训练集准确率: 38.63%\t测试集正确率: 44.40%\n",
      "当前epoch: 2 [32000/60000 (53%)]\t损失: 2.248378\t训练集准确率: 39.64%\t测试集正确率: 42.68%\n",
      "当前epoch: 2 [38400/60000 (64%)]\t损失: 2.242829\t训练集准确率: 40.40%\t测试集正确率: 42.91%\n",
      "当前epoch: 2 [44800/60000 (75%)]\t损失: 2.211833\t训练集准确率: 40.42%\t测试集正确率: 38.33%\n",
      "当前epoch: 2 [51200/60000 (85%)]\t损失: 2.193463\t训练集准确率: 40.27%\t测试集正确率: 43.69%\n",
      "当前epoch: 2 [57600/60000 (96%)]\t损失: 2.195739\t训练集准确率: 40.52%\t测试集正确率: 43.72%\n"
     ]
    }
   ],
   "source": [
    "# 实例化\n",
    "net = CNN() \n",
    "#损失函数\n",
    "criterion = nn.CrossEntropyLoss() \n",
    "#优化器\n",
    "optimizer = optim.SGD(net.parameters(), lr=0.001) #定义优化器，普通的随机梯度下降算法\n",
    "\n",
    "#开始训练循环\n",
    "for epoch in range(num_epochs):\n",
    "    #当前epoch的结果保存下来\n",
    "    train_rights = [] \n",
    "    \n",
    "    for batch_idx, (data, target) in enumerate(train_loader):  #针对容器中的每一个批进行循环\n",
    "        net.train()                             \n",
    "        output = net(data) \n",
    "        loss = criterion(output, target) \n",
    "        optimizer.zero_grad() \n",
    "        loss.backward() \n",
    "        optimizer.step() \n",
    "        right = accuracy(output, target) \n",
    "        train_rights.append(right) \n",
    "\n",
    "    \n",
    "        if batch_idx % 100 == 0: \n",
    "            \n",
    "            net.eval() \n",
    "            val_rights = [] \n",
    "            \n",
    "            for (data, target) in test_loader:\n",
    "                output = net(data) \n",
    "                right = accuracy(output, target) \n",
    "                val_rights.append(right)\n",
    "                \n",
    "            #准确率计算\n",
    "            train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))\n",
    "            val_r = (sum([tup[0] for tup in val_rights]), sum([tup[1] for tup in val_rights]))\n",
    "\n",
    "            print('当前epoch: {} [{}/{} ({:.0f}%)]\\t损失: {:.6f}\\t训练集准确率: {:.2f}%\\t测试集正确率: {:.2f}%'.format(\n",
    "                epoch, batch_idx * batch_size, len(train_loader.dataset),\n",
    "                100. * batch_idx / len(train_loader), \n",
    "                loss.data, \n",
    "                100. * train_r[0].numpy() / train_r[1], \n",
    "                100. * val_r[0].numpy() / val_r[1]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
